package redis

import (
	"fmt"
	"os"
	"sync/atomic"
	"time"

	"github.com/garyburd/redigo/redis"

	"mutu/misc/conf"
	"mutu/misc/log"
)

const (
	DEF_PASSWD   = ""
	DEF_MAX_CONN = 200
	DEF_TIMEOUT  = 3
	DEF_DEBUG    = false
)

var (
	DEF_ADDRS = []string{"127.0.0.1:6379"}
	Pool      *Redb
)

type Redb struct {
	Pools   []*redis.Pool
	Count   int
	Counter int64
	Debug   bool
}

func (p *Redb) GetConn() ConnWrap {
	var conn redis.Conn
	if p.Count == 1 {
		conn = p.Pools[0].Get()
	} else {
		counter := p.inrcCounter()
		if counter >= 100000 {
			p.resetCounter()
			counter = 0
		}
		index := int(counter) % p.Count
		conn = p.Pools[index].Get()
	}
	return ConnWrap{
		Conn: conn,
		rdb:  p,
	}
}

func (p *Redb) inrcCounter() int64 {
	return atomic.AddInt64(&p.Counter, 1)
}
func (p *Redb) resetCounter() {
	atomic.StoreInt64(&p.Counter, 0)
}

type ConnWrap struct {
	Conn redis.Conn
	rdb  *Redb
}

func (p ConnWrap) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
	if p.rdb.Debug {
		start := time.Now()
		reply, err = p.Conn.Do(commandName, args...)
		end := time.Now()
		use := end.Sub(start)
		log.Log.Debug("REDIS::%s, USE TIME: %v, PARAMS: %s", commandName, use, args)
		log.Log.Debug("RESULT: %s, ERROR: %v", reply, err)
	} else {
		reply, err = p.Conn.Do(commandName, args...)
	}
	return reply, err
}

func (p ConnWrap) Close() error {
	return p.Conn.Close()
}

func newRedisPool(addr string, password string, connCount int, timeout int, wait bool) *redis.Pool {
	return &redis.Pool{
		Wait:        wait,
		MaxIdle:     connCount,
		IdleTimeout: time.Second * time.Duration(timeout),
		Dial: func() (redis.Conn, error) {
			c, err := redis.Dial("tcp", addr)
			if err != nil {
				return nil, err
			}
			if len(password) > 0 {
				if _, err := c.Do("AUTH", password); err != nil {
					c.Close()
					return nil, err
				}
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			_, err := c.Do("PING")
			return err
		},
	}
}

func createRedb(addrs []string, count int, password string, connCount int, timeout int, debug bool) (*Redb, error) {
	redb := &Redb{}
	redb.Count = count
	redb.Counter = 0
	redb.Debug = debug
	redb.Pools = make([]*redis.Pool, count)
	for i := 0; i < count; i++ {
		redb.Pools[i] = newRedisPool(addrs[i], password, connCount, timeout, false)

	}
	return redb, nil
}

func init() {
	addrs, count := conf.GetStrings("RedisAddrs", DEF_ADDRS)
	passwd := conf.GetString("RedisPasswd", DEF_PASSWD)
	maxconn := conf.GetInt("RedisMaxConn", DEF_MAX_CONN)
	timeout := conf.GetInt("RedisTimeout", DEF_TIMEOUT)
	debug := conf.GetBool("RedisDebug", DEF_DEBUG)

	var err error
	if Pool, err = createRedb(addrs, count, passwd, maxconn, timeout, debug); err != nil {
		fmt.Printf("init redis pool  %s error, %v\n", addrs, err)
		os.Exit(1)
	}
}
